package org.myspringmvc.context;

import lombok.Data;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.dom4j.Document;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import org.myspringmvc.stereotype.Component;
import org.myspringmvc.stereotype.Controller;
import org.myspringmvc.util.ObjectUtil;
import org.myspringmvc.web.bind.annotation.RequestMapping;
import org.myspringmvc.web.constant.SystemConstant;
import org.myspringmvc.web.method.HandlerMethod;
import org.myspringmvc.web.servlet.HandlerAdapter;
import org.myspringmvc.web.servlet.HandlerInterceptor;
import org.myspringmvc.web.servlet.HandlerMapping;
import org.myspringmvc.web.servlet.ViewResolver;
import org.myspringmvc.web.servlet.mvc.RequestMappingInfo;

import java.io.File;
import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.URLDecoder;
import java.nio.charset.Charset;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;

/**
 * @author 莫磊
 * @date 2025/8/13
 * @description spring的IoC容器
 */
@Data
@Slf4j
public class ApplicationContext {

    private Map<String, Object> beanMap = new HashMap<>();

    private String configPath;

    public ApplicationContext(String configPath) {
        // 解析xml文件
        SAXReader reader = new SAXReader();

        try {
            Document document = reader.read(new File(configPath));
            // 组件扫描
            Element componentScanElement = (Element) document.selectSingleNode("/beans/component-scan");
            componentScan(componentScanElement);
            // 创建视图解析器
            List<Element> beans = (List<Element>) document.selectNodes("/beans/bean");
            createViewResolver(beans);
            // 创建拦截器
            Element interceptors = (Element) document.selectSingleNode("/beans/interceptors");
            createInterceptors(interceptors);
            // 创建HandlerMapping
            createHandlerMapping();
            // 创建HandlerAdapter
            createHandlerAdapter();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 组件扫描
     * @param componentScanElement
     */
    private void componentScan(Element componentScanElement) {
        // 获取包名
        String basePackage = componentScanElement.attributeValue(SystemConstant.BASE_PACKAGE);
        createBeanForAnnotation(basePackage, Component.class);
    }

    /**
     * 通过注解创建Bean
     * @param basePackage 包名
     * @param annotationType 注解类型
     */
    private void createBeanForAnnotation(String basePackage, Class<?> annotationType) {
        createBeanForPackage(basePackage, annotationType, null);
    }

    /**
     * 获取请求映射信息集合
     */
    private Map<RequestMappingInfo, HandlerMethod> getRequestMappingInfoAndHandlerMethod() {
        Map<RequestMappingInfo, HandlerMethod> requestMappingInfoHandlerMethodMap = new HashMap<>();
        this.beanMap.values().forEach(bean -> {
            if (bean.getClass().isAnnotationPresent(Controller.class)) {
                // 获取类中所有方法
                Method[] methods = bean.getClass().getMethods();
                for (Method method : methods) {
                    // 判断是否由RequestMapping注解
                    if (method.isAnnotationPresent(RequestMapping.class)) {
                        RequestMapping requestMapping = method.getAnnotation(RequestMapping.class);
                        // 通过requestMapping注解封装RequestMappingInfo对象
                        RequestMappingInfo requestMappingInfo = new RequestMappingInfo();
                        requestMappingInfo.setRequestMethod(requestMapping.method());
                        requestMappingInfo.setRequestURI(requestMapping.value());
                        // 封装HandlerMethod对象
                        HandlerMethod handlerMethod = new HandlerMethod();
                        handlerMethod.setHandler(bean);
                        handlerMethod.setMethod(method);

                        // 放入集合中
                        requestMappingInfoHandlerMethodMap.put(requestMappingInfo, handlerMethod);
                    }
                }
            }
        });
        return requestMappingInfoHandlerMethodMap;
    }

    /**
     * 根据包路径转换为文件
     * @param basePackage 包路径
     * @return
     */
    private static File packageToFile(String basePackage) {
        String basePath = basePackage.replaceAll("\\.", "/");
        log.debug("包的绝对路径：{}", basePath);
        // 获取绝对路径
        String absolutePath = Thread.currentThread().getContextClassLoader().getResource(basePath).getPath();
        absolutePath = URLDecoder.decode(absolutePath, Charset.defaultCharset());
        log.debug("包的绝对路径：{}", absolutePath);
        // 封装为文件
        File file = new File(absolutePath);
        return file;
    }

    /**
     * 创建bean，加载指定文件下的所有类
     * @param file 文件
     * @param basePackage 包名
     * @param annotationType 是否进行注解的校验, 为null则表示不需要进行注解校验
     * @param interfaceType 是否进行接口的校验, 为null则表示不需要进行接口校验
     * @param constructorParams 构造函数的参数, 为null则表示无参构造函数
     */
    private void createBean(File file, String basePackage, Class<?> annotationType, Class<?> interfaceType, Object... constructorParams) {
        for (File listFile : Objects.requireNonNull(file.listFiles())) {
            if (listFile.isDirectory()) {
                createBean(listFile, basePackage + "." + listFile.getName(), annotationType, interfaceType, constructorParams);
            } else {
                // 是文件，判断是否为class文件，是的话创建对象
                if (listFile.getName().endsWith(SystemConstant.SUFFIX_CLASS)) {
                    String className = basePackage + "." + listFile.getName().replaceAll(SystemConstant.SUFFIX_CLASS, "");
                    // 创建对象
                    Class<?> clazz = null;
                    try {
                        clazz = Class.forName(className);
                        // 判断是否使用注解Component让其归容器管理
                        if(!Objects.isNull(annotationType) && !isComponentAnnotation(clazz)) {
                            continue;
                        }
                        // 判断是否使用接口
                        if (!Objects.isNull(interfaceType) && !interfaceType.isAssignableFrom(clazz)) {
                            continue;
                        }

                        // 判断是使用无参构造函数还是有参构造函数
                        Object bean = null;
                        if (!Objects.isNull(constructorParams) && constructorParams.length > 0) {
                            Class<?>[] parameterTypes = new Class[constructorParams.length];
                            for (int i = 0; i < constructorParams.length; i++) {
                                parameterTypes[i] = constructorParams[i].getClass();
                            }
                            Constructor<?> declaredConstructor = clazz.getDeclaredConstructor(parameterTypes);
                            bean = declaredConstructor.newInstance(constructorParams);
                        } else {
                            bean = clazz.getDeclaredConstructor().newInstance();
                        }
                        beanToApplicationContentMap(clazz, bean);
                    } catch (Exception e) {
                        throw new RuntimeException(e);
                    }
                }
            }
        }
    }

    /**
     * 将bean放入容器中
     * @param clazz
     * @param bean
     */
    private void beanToApplicationContentMap(Class<?> clazz, Object bean) {
        // 判断bean的名字是否重复，如果重复则抛出异常
        String beanName = clazz.getSimpleName().substring(0, 1).toLowerCase() + clazz.getSimpleName().substring(1);
        if (beanMap.containsKey(beanName)) {
            throw new RuntimeException("bean的名字重复");
        }
        log.debug("创建bean：{}，{}", beanName, clazz);
        beanMap.put(beanName, bean);
    }

    /**
     * 判断是否使用了Component注解
     * @param clazz
     */
    private boolean isComponentAnnotation(Class<?> clazz) {
        Annotation[] declaredAnnotations = clazz.getDeclaredAnnotations();
        for (Annotation declaredAnnotation : declaredAnnotations) {
            if (declaredAnnotation.annotationType().isAnnotationPresent(Component.class)) {
                return true;
            }
            // 继续向上获取父级注解
            return isComponentAnnotation(declaredAnnotation.annotationType());

        }
        return false;
    }

    /**
     * 创建视图解析器
     * @param beans
     */
    @SneakyThrows
    private void createViewResolver(List<Element> beans) {
        for (Element bean : beans) {
            // 获取视图解析器的bean配置
            createBeanForBeanTag(bean, ViewResolver.class);
        }
    }

    /**
     * 通过bean标签创建对象
     * @param bean
     * @param clazzType
     * @return
     * @throws ClassNotFoundException
     * @throws InstantiationException
     * @throws IllegalAccessException
     * @throws InvocationTargetException
     * @throws NoSuchMethodException
     */
    private void createBeanForBeanTag(Element bean, Class<?> clazzType) throws ClassNotFoundException, InstantiationException, IllegalAccessException, InvocationTargetException, NoSuchMethodException {

            String qName = bean.attributeValue(SystemConstant.CLASS_TAG);
            Class clazz = Class.forName(qName);
            // 判断是否需要实现某个接口
            if (clazzType == null || clazzType.isAssignableFrom(clazz)) {
                // 实现了指定接口
                Object obj = clazz.getDeclaredConstructor().newInstance();
                // 设置属性
                List<Element> list = bean.selectNodes(SystemConstant.PROPERTY_TAG);
                // 获取属性名和属性值
                for (Element element : list) {
                    String attributeName = element.attributeValue(SystemConstant.NAME_ATTRIBUTE);
                    String attributeValue = element.attributeValue(SystemConstant.VALUE_ATTRIBUTE);
                    // 根据属性属性名和值设置属性
                    ObjectUtil.setFieldForBean(obj, attributeName, attributeValue);
                }
                // 加入spring的IoC容器
                beanToApplicationContentMap(clazz, obj);
            }
    }

    /**
     * 创建拦截器
     * @param interceptors
     */
    @SneakyThrows
    private void createInterceptors(Element interceptors) throws ClassNotFoundException, InvocationTargetException, InstantiationException, IllegalAccessException, NoSuchMethodException {
        // 获取拦截器中的bean配置
        List<Element> list = interceptors.elements("bean");
        for (Element element : list) {
            createBeanForBeanTag(element, HandlerInterceptor.class);
        }
    }

    /**
     * 创建HandlerMapping
     */
    private void createHandlerMapping() {
        Map<RequestMappingInfo, HandlerMethod> requestMappingInfoAndHandlerMethod = getRequestMappingInfoAndHandlerMethod();
        createBeanForInterface(SystemConstant.PACKAGE_HANDLER,
                HandlerMapping.class,
                requestMappingInfoAndHandlerMethod);

    }

    /**
     * 通过接口创建指定包下的对象
     * @param packageName 指定的包名
     * @param interfaceType 接口类型
     * @param constructorParams 构造方法参数
     */
    private void createBeanForInterface(String packageName, Class<?> interfaceType, Object... constructorParams) {
        createBeanForPackage(packageName, null, interfaceType, constructorParams);

    }

    /**
     * 创建HandlerAdapter
     */
    private void createHandlerAdapter() {
        createBeanForInterface(SystemConstant.PACKAGE_HANDLER, HandlerAdapter.class);

    }

    /**
     * 根据报名创建类
     */
    private void createBeanForPackage(String packageName, Class<?> annotationType, Class<?> interfaceType, Object... constructorParams) {
        // 获取指定路径下的类
        File file = packageToFile(packageName);
        // 获取指定路径下的类，该路径下的类不需要使用Component注解进行标注
        createBean(file, packageName, annotationType, interfaceType, constructorParams);
    }

    /**
     * 获取bean
     * @param beanName
     * @return
     */
    public Object getBean(String beanName) {
        return beanMap.get(beanName);
    }

    public List<?> getBeansOfType(Class<?> clazz) {
        List<?> collect = beanMap.values().stream().filter(bean -> clazz.isAssignableFrom(bean.getClass())).collect(Collectors.toList());
        return collect;
    }
}
